package httpyt

import (
	"context"
	"gitee.com/zhancaihua/goyt/core/logyt"
	"gitee.com/zhancaihua/goyt/core/proc"
	"gitee.com/zhancaihua/goyt/httpyt/middleware"
	"github.com/gin-gonic/gin"
	"net"
	"net/http"
	"strings"
	"time"
)

const (
	Debug   Mode = "debug"
	Release Mode = "release"
)

type (
	Mode         string
	HttpServerYt struct {
		s  *http.Server
		g  *gin.Engine
		op HttpServerOptions
	}
	HttpServerOptions struct {
		mode              Mode
		Addr              string
		ReadTimeout       time.Duration
		ReadHeaderTimeout time.Duration
		WriteTimeout      time.Duration
		IdleTimeout       time.Duration
		BaseContext       func(net.Listener) context.Context

		//config for gin
		RedirectTrailingSlash bool
		//RedirectFixedPath=false
		//HandleMethodNotAllowed=false
		//UseRawPath=false
		//RemoveExtraSlash=false
		//UnescapePathValues=true

		ForwardedByClientIP bool
		TrustedPlatform     string
		RemoteIPHeaders     []string
		TrustedProxies      []string

		// UseH2C=false
		// ContextWithFallback true

		tls     bool
		certPem string
		keyPem  string
	}
	HttpServerOption func(*HttpServerOptions)
)

func WithMode(mode Mode) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.mode = mode
	}
}
func WithTLS() HttpServerOption {
	return func(options *HttpServerOptions) {
		options.tls = true
	}
}
func WithCertPem(path string) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.certPem = path
	}
}
func WithKeyPem(path string) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.keyPem = path
	}
}
func WithAddr(addr string) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.Addr = addr
	}
}
func WithReadTimeout(timeout time.Duration) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.ReadTimeout = timeout
	}
}
func WithReadHeaderTimeout(timeout time.Duration) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.ReadHeaderTimeout = timeout
	}
}
func WithWriteTimeout(timeout time.Duration) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.WriteTimeout = timeout
	}
}
func WithIdleTimeout(timeout time.Duration) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.IdleTimeout = timeout
	}
}

func WithBaseContext(ctx context.Context) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.BaseContext = func(listener net.Listener) context.Context {
			return ctx
		}
	}
}
func WithBaseContextFunc(fn func(net.Listener) context.Context) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.BaseContext = fn
	}
}
func WithRedirectTrailingSlash() HttpServerOption {
	return func(options *HttpServerOptions) {
		options.RedirectTrailingSlash = true
	}
}

func WithTrustedPlatform(platform string) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.TrustedPlatform = platform
	}
}
func WithForwardedByClientIP() HttpServerOption {
	return func(options *HttpServerOptions) {
		options.ForwardedByClientIP = true
	}
}

func WithRemoteIPHeaders(hs ...string) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.RemoteIPHeaders = append(options.RemoteIPHeaders, hs...)
	}
}
func WithTrustedProxies(ps ...string) HttpServerOption {
	return func(options *HttpServerOptions) {
		options.TrustedProxies = append(options.TrustedProxies, ps...)
	}
}

func (y *HttpServerYt) Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup {
	return y.g.Group(relativePath, handlers...)
}
func (y *HttpServerYt) Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
	return y.g.Handle(httpMethod, relativePath, handlers...)
}

// POST is a shortcut for router.Handle("POST", path, handle).
func (y *HttpServerYt) POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
	return y.Handle(http.MethodPost, relativePath, handlers...)
}

// GET is a shortcut for router.Handle("GET", path, handle).
func (y *HttpServerYt) GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
	return y.Handle(http.MethodGet, relativePath, handlers...)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle).
func (y *HttpServerYt) DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
	return y.Handle(http.MethodDelete, relativePath, handlers...)
}

// PATCH is a shortcut for router.Handle("PATCH", path, handle).
func (y *HttpServerYt) PATCH(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
	return y.Handle(http.MethodPatch, relativePath, handlers...)
}

// PUT is a shortcut for router.Handle("PUT", path, handle).
func (y *HttpServerYt) PUT(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
	return y.Handle(http.MethodPut, relativePath, handlers...)
}

// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
func (y *HttpServerYt) OPTIONS(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
	return y.Handle(http.MethodOptions, relativePath, handlers...)
}

// HEAD is a shortcut for router.Handle("HEAD", path, handle).
func (y *HttpServerYt) HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
	return y.Handle(http.MethodHead, relativePath, handlers...)
}
func (y *HttpServerYt) Use(middleware ...gin.HandlerFunc) gin.IRoutes {
	return y.g.Use(middleware...)
}

// Any registers a route that matches all the HTTP methods.
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
func (y *HttpServerYt) Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
	return y.g.Any(relativePath, handlers...)
}

// NewHttpServerYt
//
//	engine := &Engine{
//			RouterGroup: RouterGroup{
//				Handlers: nil,
//				basePath: "/",
//				root:     true,
//			},
//			FuncMap:                template.FuncMap{},
//			RedirectTrailingSlash:  true,
//			RedirectFixedPath:      false,
//			HandleMethodNotAllowed: false,
//			ForwardedByClientIP:    true,
//			RemoteIPHeaders:        []string{"X-Forwarded-For", "X-Real-IP"},
//			TrustedPlatform:        defaultPlatform,
//			UseRawPath:             false,
//			RemoveExtraSlash:       false,
//			UnescapePathValues:     true,
//			MaxMultipartMemory:     defaultMultipartMemory,
//			trees:                  make(methodTrees, 0, 9),
//			delims:                 render.Delims{Left: "{{", Right: "}}"},
//			secureJSONPrefix:       "while(1);",
//			trustedProxies:         []string{"0.0.0.0/0", "::/0"},
//			trustedCIDRs:           defaultTrustedCIDRs,
//		}
//
//	RedirectTrailingSlash bool
//		//RedirectFixedPath=false
//		//HandleMethodNotAllowed=false
//		//UseRawPath=false
//		//RemoveExtraSlash=false
//		//UnescapePathValues=true
//
//		ForwardedByClientIP bool
//		TrustedPlatform string
//		RemoteIPHeaders []string
//		TrustedProxies  []string
//
//		// UseH2C=false
//		// ContextWithFallback true
func NewHttpServerYt(options ...HttpServerOption) (*HttpServerYt, error) {
	server := &HttpServerYt{}
	for _, o := range options {
		o(&server.op)
	}
	//init gin
	initMode(server)
	engine := gin.New()
	err := initEngine(server, engine)
	if err != nil {
		return nil, err
	}
	server.g = engine
	//init server
	s := &http.Server{
		Addr:                         server.op.Addr,
		Handler:                      engine,
		DisableGeneralOptionsHandler: true,
		ReadTimeout:                  server.op.ReadTimeout,
		ReadHeaderTimeout:            server.op.ReadHeaderTimeout,
		WriteTimeout:                 server.op.WriteTimeout,
		IdleTimeout:                  server.op.IdleTimeout,
		BaseContext:                  server.op.BaseContext,
	}
	server.s = s
	return server, nil
}

func initEngine(server *HttpServerYt, engine *gin.Engine) error {
	op := server.op
	engine.RedirectTrailingSlash = op.RedirectTrailingSlash
	engine.RedirectFixedPath = false
	engine.HandleMethodNotAllowed = false
	engine.UseRawPath = false
	engine.RemoveExtraSlash = false
	engine.UnescapePathValues = true
	engine.ForwardedByClientIP = op.ForwardedByClientIP
	engine.TrustedPlatform = op.TrustedPlatform
	engine.RemoteIPHeaders = append(engine.RemoteIPHeaders, op.RemoteIPHeaders...)
	if err := engine.SetTrustedProxies(op.TrustedProxies); nil != err {
		return err
	}
	engine.UseH2C = false
	engine.ContextWithFallback = true
	return nil
}

func initMode(server *HttpServerYt) {
	switch server.op.mode {
	case Debug:
		gin.SetMode(gin.DebugMode)
	case Release:
		gin.SetMode(gin.ReleaseMode)
	}
}

func (y *HttpServerYt) Start() error {
	if y.op.tls {
		return y.s.ListenAndServeTLS(y.op.certPem, y.op.keyPem)
	}
	return y.s.ListenAndServe()
}
func (y *HttpServerYt) Stop(ctx context.Context) error {
	return y.s.Shutdown(ctx)
}

type HttpServerConf struct {
	Name     string `mapstructure:"name" json:"name,omitempty"`
	ListenOn string `mapstructure:"listen_on" json:"listen_on,omitempty"`
	Tls      bool   `mapstructure:"tls" json:"tls,omitempty"`
	CertPem  string `mapstructure:"cert_pem" json:"cert_pem,omitempty"`
	KeyPem   string `mapstructure:"key_pem" json:"key_pem,omitempty"`
	Mode     Mode   `mapstructure:"mode" json:"mode,omitempty"`

	ReadTimeout           time.Duration             `mapstructure:"read_timeout" json:"read_timeout,omitempty"`
	ReadHeaderTimeout     time.Duration             `mapstructure:"read_header_timeout" json:"read_header_timeout,omitempty"`
	WriteTimeout          time.Duration             `mapstructure:"write_timeout" json:"write_timeout,omitempty"`
	IdleTimeout           time.Duration             `mapstructure:"idle_timeout" json:"idle_timeout,omitempty"`
	RedirectTrailingSlash bool                      `mapstructure:"redirect_trailing_slash" json:"redirect_trailing_slash,omitempty"`
	ForwardedByClientIP   bool                      `mapstructure:"forwarded_by_client_ip" json:"forwarded_by_client_ip,omitempty"`
	TrustedPlatform       string                    `mapstructure:"trusted_platform" json:"trusted_platform,omitempty"`
	RemoteIPHeaders       []string                  `mapstructure:"remote_ip_headers" json:"remote_ip_headers,omitempty"`
	TrustedProxies        []string                  `mapstructure:"trusted_proxies" json:"trusted_proxies,omitempty"`
	Middlewares           HttpServerMiddlewaresConf `mapstructure:"middlewares" json:"middlewares"`
}
type HttpServerMiddlewaresConf struct {
	Recover           bool     `mapstructure:"recover" json:"recover,omitempty"`
	Logger            bool     `mapstructure:"logger" json:"logger,omitempty"`
	LoggerIgnorePaths []string `mapstructure:"logger_ignore_paths" json:"logger_ignore_paths,omitempty"`
}

// SetUpServer 新建服务实例，并初始化日志，注册服务等项目
func SetUpServer(c HttpServerConf) (*HttpServerYt, error) {
	var err error
	//初始化服务
	var options = []HttpServerOption{WithAddr(c.ListenOn), WithMode(c.Mode)}
	//time
	if c.ReadTimeout > 0 {
		options = append(options, WithReadTimeout(c.ReadTimeout))
	}
	if c.ReadHeaderTimeout > 0 {
		options = append(options, WithReadHeaderTimeout(c.ReadHeaderTimeout))
	}
	if c.WriteTimeout > 0 {
		options = append(options, WithWriteTimeout(c.WriteTimeout))
	}
	if c.IdleTimeout > 0 {
		options = append(options, WithIdleTimeout(c.IdleTimeout))
	}
	//tls
	if c.Tls {
		options = append(options, WithTLS(), WithCertPem(c.CertPem), WithKeyPem(c.KeyPem))
	}
	if c.RedirectTrailingSlash {
		options = append(options, WithRedirectTrailingSlash())
	}
	if c.ForwardedByClientIP {
		options = append(options, WithForwardedByClientIP())
	}
	if tp := strings.TrimSpace(c.TrustedPlatform); "" != tp {
		options = append(options, WithTrustedPlatform(tp))
	}
	if len(c.RemoteIPHeaders) > 0 {
		var headers []string
		for _, header := range c.RemoteIPHeaders {
			if h := strings.TrimSpace(header); "" != h {
				headers = append(headers, h)
			}
		}
		if len(headers) > 0 {
			options = append(options, WithRemoteIPHeaders(headers...))
		}
	}
	if len(c.TrustedProxies) > 0 {
		var proxies []string
		for _, proxy := range c.TrustedProxies {
			if p := strings.TrimSpace(proxy); "" != p {
				proxies = append(proxies, p)
			}
		}
		if len(proxies) > 0 {
			options = append(options, WithTrustedProxies(proxies...))
		}
	}

	server, err := NewHttpServerYt(options...)
	if err != nil {
		return nil, err
	}
	//中间件
	setupMiddlewares(server, c.Middlewares)
	return server, nil
}

func setupMiddlewares(server *HttpServerYt, c HttpServerMiddlewaresConf) {
	if c.Logger {
		server.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
			SkipPaths: c.LoggerIgnorePaths,
		}))
	}
	if c.Recover {
		server.Use(middleware.Recovery())
	}
}

var shutdownTimeout = time.Second * 60

// Start 启动服务,并注册全局回收监听。此方法在服务退出后，会继续等待所有监听器执行完毕
func Start(s *HttpServerYt) (e error) {
	//回收监听
	waitForCalled := proc.AddWrapUpListener(func() {
		ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
		_ = s.Stop(ctx)
		cancel()
		logyt.InfoLog("server stopped")
	})
	defer func() {
		if a := recover(); nil != a {
			logyt.ErrorLog("server stop", logyt.LogField{
				Key:   "err",
				Value: a,
			})
		}
		if nil != e {
			logyt.ErrorLog("server stop", logyt.LogField{
				Key:   "err",
				Value: e,
			})
		}
		//等待所有资源回收，继续等待所有监听器执行完毕
		waitForCalled()
	}()
	e = s.Start()
	return
}

// ShutDown 由于服务器目前会被信号主动关闭，所以当前无事可做。你可以用他收集一些回调方法统一调用
func ShutDown(s *HttpServerYt, fs ...func(*HttpServerYt)) {
	for _, f := range fs {
		f(s)
	}
}
